{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Dragon curve example from the [L-systems](../../topics/geometry/lsystems.ipynb) topic notebook in ``examples/topics/geometry``.\n",
    "\n",
    "Most examples work across multiple plotting backends, this example is also available for:\n",
    "* [Matplotlib - dragon_curve](../matplotlib/dragon_curve.ipynb)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import holoviews as hv\n",
    "import numpy as np\n",
    "hv.extension('bokeh')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## L-system definition"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The following class is a simplified version of the approach used in the [L-systems](../../topics/geometry/lsystems.ipynb) notebook, made specifically for plotting the [Dragon Curve](https://en.wikipedia.org/wiki/Dragon_curve)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class DragonCurve(object):\n",
    "    \"L-system agent that follows rules to generate the Dragon Curve\"\n",
    "    \n",
    "    initial ='FX'\n",
    "    productions = {'X':'X+YF+', 'Y':'-FX-Y'}\n",
    "    dragon_rules = {'F': lambda t,d,a: t.forward(d),\n",
    "                    'B': lambda t,d,a: t.back(d),\n",
    "                    '+': lambda t,d,a: t.rotate(-a),\n",
    "                    '-': lambda t,d,a: t.rotate(a),\n",
    "                    'X':lambda t,d,a: None,\n",
    "                    'Y':lambda t,d,a: None }\n",
    "    \n",
    "    def __init__(self, x=0,y=0, iterations=1):\n",
    "        self.heading = 0\n",
    "        self.distance = 5\n",
    "        self.angle = 90\n",
    "        self.x, self.y = x,y\n",
    "        self.trace = [(self.x, self.y)]\n",
    "        self.process(self.expand(iterations), self.distance, self.angle)\n",
    "        \n",
    "    def process(self, instructions, distance, angle):\n",
    "        for i in instructions:          \n",
    "            self.dragon_rules[i](self, distance, angle)\n",
    "        \n",
    "    def expand(self, iterations):\n",
    "        \"Expand an initial symbol with the given production rules\"\n",
    "        expansion = self.initial\n",
    "        \n",
    "        for i in range(iterations):\n",
    "            intermediate = \"\"\n",
    "            for ch in expansion:\n",
    "                intermediate = intermediate + self.productions.get(ch,ch)\n",
    "            expansion = intermediate\n",
    "        return expansion\n",
    "\n",
    "    def forward(self, distance):\n",
    "        self.x += np.cos(2*np.pi * self.heading/360.0)\n",
    "        self.y += np.sin(2*np.pi * self.heading/360.0)\n",
    "        self.trace.append((self.x,self.y))\n",
    "    \n",
    "    def rotate(self, angle):\n",
    "        self.heading += angle\n",
    "        \n",
    "    def back(self, distance):\n",
    "        self.heading += 180\n",
    "        self.forward(distance)\n",
    "        self.heading += 180\n",
    "        \n",
    "    @property\n",
    "    def path(self):\n",
    "        return hv.Path([self.trace])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Plot"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%%opts Path {+framewise} [xaxis=None yaxis=None title_format=''] (color='black' line_width=1)\n",
    "\n",
    "def pad_extents(path):\n",
    "    \"Add 5% padding around the path\"\n",
    "    minx, maxx = path.range('x')\n",
    "    miny, maxy = path.range('y')\n",
    "    xpadding = ((maxx-minx) * 0.1)/2\n",
    "    ypadding = ((maxy-miny) * 0.1)/2\n",
    "    path.extents = (minx-xpadding, miny-ypadding, maxx+xpadding, maxy+ypadding)\n",
    "    return path\n",
    "    \n",
    "hmap = hv.HoloMap(kdims=['Iteration'])\n",
    "for i in range(7,17):\n",
    "    path = DragonCurve(-200, 0, i).path\n",
    "    hmap[i] = pad_extents(path)\n",
    "hmap"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
